﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace PersianCalendarControl.Controls
{
    public enum TriggerTypes { LeftClick, RightClick, Hover }

    public class PopupMenu //: ListBox
    {
        DispatcherTimer _timerOpen;
        DispatcherTimer _timerClose;
        public static List<PopupMenu> OpenMenuList = new List<PopupMenu>();
        public event EventHandler<MouseEventArgs> Opening;
        public event EventHandler<MouseEventArgs> Showing;
        public event EventHandler<MouseEventArgs> Shown;
        public event EventHandler<MouseEventArgs> Closing;
        public int HoverShowDelay { get; set; }

        public Grid RootGrid { get; set; }
        public Popup MenuPopup { get; set; }
        public ComposedCalendarViewControl Calendar { get; set; }
        public Effect ListBoxEffect { get; set; }
        public Effect ItemsEffect { get; set; }

        IEnumerable<UIElement> ClickedElements { get; set; }

        FrameworkElement HoveredTriggerElement { get; set; }
        private List<FrameworkElement> _hoverTriggerElements = new List<FrameworkElement>();
        public double OffsetX { get; set; }
        public double OffsetY { get; set; }
        public bool ShowOnRight { get; set; }


        #region Constructors

        public PopupMenu()
            : this(0, 0)
        { }

        public PopupMenu(double offsetX, double offsetY)
            : this(new ComposedCalendarViewControl { Background = new SolidColorBrush(Color.FromArgb(255, 240, 240, 240)) },
                 null, offsetX, offsetY)
        { }

        public PopupMenu(ComposedCalendarViewControl calendar)
            : this(calendar, null, 0, 0)
        { }

        public static implicit operator PopupMenu(ComposedCalendarViewControl calendar)
        {
            return new PopupMenu(calendar);
        }

        public PopupMenu(ComposedCalendarViewControl calendar, double offsetX, double offsetY)
            : this(calendar, null, offsetX, offsetY)
        { }

        #endregion

        public PopupMenu(ComposedCalendarViewControl calendar, Effect itemsEffect, double offsetX, double offsetY)
        {
            OffsetX = offsetX;
            OffsetY = offsetY;

            HoverShowDelay = 150;

            MenuPopup = new Popup();

            Calendar = calendar;
            if (Calendar.Effect == null)
                Calendar.Effect = new DropShadowEffect { Color = Colors.Black, BlurRadius = 4, Opacity = 0.5, ShadowDepth = 2 };

            ItemsEffect = itemsEffect;

            MenuPopup.Child = GeneratePopupContent();

            // Enable stretching on each calendar item
//            Style style = new Style(typeof(ListBoxItem));
//            style.Setters.Add(new Setter(ListBoxItem.HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch));
            //style.Setters.Add(new Setter(ListBoxItem.HorizontalAlignmentProperty, HorizontalAlignment.Stretch));
           
        }

        private Grid GeneratePopupContent()
        {
            Grid outerGrid = new Grid
            {
                Width = Application.Current.Host.Content.ActualWidth,
                Height = Application.Current.Host.Content.ActualHeight,
                Background = new SolidColorBrush(Colors.Transparent)
            };

            Canvas outerCanvas = new Canvas
            {
                Width = outerGrid.Width,
                Height = outerGrid.Height
            };

            RootGrid = new Grid
            {
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
            };

            outerGrid.Children.Add(outerCanvas);
            outerCanvas.Children.Add(RootGrid);

            if (Calendar.Parent != null)
            {
                if (Calendar.Parent is Panel)
                    (Calendar.Parent as Panel).Children.Remove(Calendar);
                else
                    throw new Exception("The template calendar must be placed in a container that inherits from the type Panel");
            }

            RootGrid.Children.Add(Calendar);

            // Close the menu when a click is made outside popup itself
            outerGrid.MouseLeftButtonDown += delegate
            {
                this.Close();
            };

            outerGrid.MouseMove += (sender, e) =>
            {
                if (HoveredTriggerElement != null)
                {
                    List<FrameworkElement> triggerElementList;
                    triggerElementList = new List<FrameworkElement> { HoveredTriggerElement, RootGrid };
                    // Textblocks tend to have a variable width depending on the text length
                    // In this case the parent element is also used for hit testing to avoid limiting the hover region to the text only region.
                    if (HoveredTriggerElement is TextBlock)
                        if (double.IsNaN(HoveredTriggerElement.Width) && double.IsInfinity(HoveredTriggerElement.MaxWidth))
                            triggerElementList.Add(HoveredTriggerElement.Parent as FrameworkElement);

                    //if (_timerClose == null)
                    //{
                    //    _timerClose = new DispatcherTimer();
                    //    _timerClose.Tick += delegate
                    //    {
                    //_timerClose.Stop();
                    //        HoveredTriggerElement = null;
                    //        this.Close();
                    //};
                    //_timerClose.Interval = TimeSpan.FromMilliseconds(300);
                    //}

                    //if (!_timerClose.IsEnabled && !HitTestAny(e, triggerElementList.ToArray()))
                    //{
                    //    _timerClose.Start();
                    //}

                    if (!HitTestAny(e, triggerElementList.ToArray()))
                    {
                        HoveredTriggerElement = null;
                        this.Close();
                    }
                }

            };

            outerGrid.MouseRightButtonDown += (sender, e) =>
            {
                if (!HitTest(e, RootGrid))
                    this.Close();
                e.Handled = true;
            };

            return outerGrid;
        }

        #region Static Methods

        //private static bool HitTest(Point point, FrameworkElement element)
        //{
        //    List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(point, element) as List<UIElement>;
        //    return (hits.Contains(element));
        //}

        public static bool HitTestAny(MouseEventArgs e, params FrameworkElement[] elements)
        {
            foreach (var element in elements)
                if (HitTest(e, element))
                    return true;
            return false;
        }

        public static bool HitTest(MouseEventArgs e, FrameworkElement element)
        {
            //HtmlPage.Document.SetProperty("Title", e.GetSafePosition(null).X);
            return element.GetBoundsRelativeTo(Application.Current.RootVisual).HasValue
                && IsPointWithinBounds(e.GetSafePosition(null), element.GetBoundsRelativeTo(Application.Current.RootVisual).Value);
        }

        public static bool HitTest(Point point, FrameworkElement element)
        {
            return IsPointWithinBounds(point, element.GetBoundsRelativeTo(Application.Current.RootVisual).Value);
        }

        public static bool IsPointWithinBounds(Point pt, Rect b)
        {
            return (pt.X > b.Left && pt.X < b.Right && pt.Y > b.Top && pt.Y < b.Bottom);
        }

        public static Point GetAbsoluteElementPos(FrameworkElement element)
        {
            return element.TransformToVisual(null).Transform(new Point());
        }

        public static Point GetAbsoluteMousePos(MouseEventArgs e)
        {
            // This will not work for MouseLeave events
            return Application.Current.RootVisual.TransformToVisual(null).Transform(e.GetPosition(Application.Current.RootVisual));
        }

        //public static Point GetAbsoluteMousePos(MouseEventArgs e, FrameworkElement element)
        //{
        //    return element.TransformToVisual(null).Transform(e.GetPosition(element));
        //}

        public static FrameworkElement GetRootLayoutElement(FrameworkElement element)
        {
            while ((element != null && element.Parent != null && element.Parent is FrameworkElement))
                element = (FrameworkElement)element.Parent;
            return element;
        }

        public static Rectangle CreateSeperator(string tag)
        {
            Rectangle rectangle = new Rectangle
            {
                Height = 2,
                Margin = new Thickness(-3, 0, 0, 0),
                Fill = MakeColorGradient(Color.FromArgb(4, 0, 0, 0), Color.FromArgb(100, 255, 255, 255), 90)
            };
            DockPanel.SetDock(rectangle, Dock.Top);

            if (tag != null) rectangle.Tag = tag;
            return rectangle;
        }

        public static LinearGradientBrush MakeColorGradient(Color startColor, Color endColor, double angle)
        {
            GradientStopCollection gradientStopCollection = new GradientStopCollection();
            gradientStopCollection.Add(new GradientStop { Color = startColor, Offset = 0 });
            gradientStopCollection.Add(new GradientStop { Color = endColor, Offset = 1 });
            LinearGradientBrush brush = new LinearGradientBrush(gradientStopCollection, angle);
            return brush;
        }

        #endregion





        public void AddTrigger(Button triggerElement)
        {
            triggerElement.MouseLeftButtonUp += (sender, e) =>
            {
                if (MenuPopup.IsOpen)
                {
                    this.Close();
                }
                else
                {
                    Point mousePos = GetAbsoluteElementPos(triggerElement);
                    mousePos.Y += triggerElement.ActualHeight;
                    this.Open(mousePos, 0, sender, e);
                }
            };
        }


        public void AddTrigger(TriggerTypes triggerType, params UIElement[] triggerElements)
        {
            foreach (FrameworkElement triggerElement in triggerElements)
            {
                switch (triggerType)
                {

                    case TriggerTypes.RightClick:
                        triggerElement.MouseRightButtonDown += (sender, e) =>
                        {
                            e.Handled = true;
                        };

                        triggerElement.MouseRightButtonUp += (sender, e) =>
                        {
                            Point mousePos = GetAbsoluteMousePos(e);
                            this.Open(mousePos, 0, sender, e);
                        };
                        break;

                    case TriggerTypes.Hover:
                        triggerElement.MouseEnter += (sender, e) =>
                        {
                            if (_timerClose != null)
                                _timerClose.Stop();

                            HoveredTriggerElement = sender as FrameworkElement;

                            Point elemPos = GetAbsoluteElementPos(HoveredTriggerElement);
                            if (ShowOnRight)
                                elemPos.X += HoveredTriggerElement.ActualWidth - 1;
                            else
                                elemPos.Y += HoveredTriggerElement.ActualHeight;

                            this.Open(elemPos, 200, sender, e);

                        };
                        _hoverTriggerElements.Add(triggerElement);
                        break;

                    case TriggerTypes.LeftClick:
                        triggerElement.MouseLeftButtonDown += (sender, e) =>
                        {
                            if (MenuPopup.IsOpen)
                            {
                                this.Close();
                            }
                            else
                            {
                                Point mousePos = GetAbsoluteElementPos(triggerElement);
                                mousePos.Y += triggerElement.ActualHeight;
                                this.Open(mousePos, 0, sender, e);
                            }
                        };
                        break;
                }
            }
        }

        public void Open(Point mousePos, int showDelayPeriod, object sender, MouseEventArgs e)
        {
            if (!MenuPopup.IsOpen)
            {
                if (!OpenMenuList.Contains(this))
                    OpenMenuList.Add(this);
                UIElement LayoutRoot = GetRootLayoutElement((FrameworkElement)sender);
                ClickedElements = VisualTreeHelper.FindElementsInHostCoordinates(mousePos, LayoutRoot);
                RootGrid.Margin = new Thickness(mousePos.X + OffsetX, mousePos.Y + OffsetY, 0, 0);
                

                // Invoking the event via a dispatcher to make sure the visual tree for our calendar is created before the event handler is called
                if (Opening != null)
                    Calendar.Dispatcher.BeginInvoke(() => Opening.Invoke(sender, e));

                RootGrid.Width = 0;

                _timerOpen = new DispatcherTimer();
                _timerOpen.Interval = TimeSpan.FromMilliseconds(showDelayPeriod);
                _timerOpen.Tick += delegate
                {
                    _timerOpen.Stop();
                    // If menu has not already been closed by hovering on the outergrid
                    if (MenuPopup.IsOpen)
                    {
                        // Remove Width = 0 constraint set originally
                        RootGrid.Width = double.NaN;
                        if (Showing != null)
                            Showing.Invoke(sender, e);
                        Animate(RootGrid, "UIElement.Opacity", 0, 1, TimeSpan.FromMilliseconds(HoverShowDelay));
                        
                        if (Shown != null)
                            Shown.Invoke(sender, e);
                    }
                };
                _timerOpen.Start();

                MenuPopup.IsOpen = true;
            }
        }



        public void CloseAllOpenMenus()
        {
            foreach (PopupMenu menu in OpenMenuList.ToArray())
                menu.Close();
        }



        public void Close()
        {
            if (Closing != null)
                Closing.Invoke(this, new EventArgs() as MouseEventArgs);
            if (_timerOpen != null)
                _timerOpen.Stop();
            MenuPopup.IsOpen = false;
            OpenMenuList.Remove(this);
        }


        public Storyboard Animate(FrameworkElement element, string targetProperty, double? from, double? to, Duration duration)
        {
            DoubleAnimation da = new DoubleAnimation { From = from, To = to, Duration = duration };
            Storyboard.SetTarget(da, element);
            Storyboard.SetTargetProperty(da, new PropertyPath(targetProperty));
            Storyboard sb = new Storyboard();
            sb.Children.Add(da);
            sb.Begin();
            return sb;
        }

       

        

        public T GetChildItem<T>(UIElement element)
        {
            foreach (object item in (element as UIElement).GetVisualChildren())
            {
                if (item != null && item is T)
                    return (T)item;
            }
            return default(T);
        }

        public T GetClickedElement<T>()
        {
            return GetElement<T>(ClickedElements);
        }

        private static T GetElement<T>(IEnumerable<UIElement> elements)
        {
            return GetElement<T>(elements, 0);
        }

        private static T GetElement<T>(IEnumerable<UIElement> elements, int index)
        {
            foreach (object elem in elements)
                if (elem is T && index-- <= 0)
                    return (T)elem;
            return default(T);
        }


        

        

        

        public static T GetContainer<T>(FrameworkElement item)
        {
            return item.GetVisualAncestors().Where(i => i is T).Select(i => (T)(object)i).FirstOrDefault();
            //do
            //{
            //    DependencyObject o = ListBox.ItemContainerGenerator.ContainerFromItem(item);
            //    item = (o ?? item.Parent) as FrameworkElement;
            //    if (item is T)
            //        return (T)(object)item;
            //}
            //while (item != null);
            //return default(T);
        }

        

        public void CloseParentPopupMenu()
        {
            // Close the parent popup if any
            foreach (var triggerElement in _hoverTriggerElements)
            {
                var parentPopup = GetContainer<PopupMenu>(triggerElement);
                if (parentPopup != null)
                    parentPopup.Close();
            }
        }

        
       
    }

}
